// c-api-examples/vad-sense-voice-c-api.c
//
// Copyright (c)  2024  Xiaomi Corporation

//
// This file demonstrates how to use VAD + SenseVoice with sherpa-onnx's C API.
// clang-format off
//
// wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/silero_vad.onnx
// wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/lei-jun-test.wav
//
// wget https://github.com/k2-fsa/sherpa-onnx/releases/download/asr-models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
// tar xvf sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
// rm sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17.tar.bz2
//
// clang-format on

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "sherpa-onnx/c-api/c-api.h"

int32_t main() {
  const char *wav_filename = "./lei-jun-test.wav";
  const char *vad_filename = "./silero_vad.onnx";
  const char *model_filename =
      "./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/model.int8.onnx";
  const char *tokens_filename =
      "./sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17/tokens.txt";
  const char *language = "auto";
  const char *provider = "cpu";
  int32_t use_inverse_text_normalization = 1;

// 从指定文件路径读取音频波形数据，返回SherpaOnnxWave结构体指针
  const SherpaOnnxWave *wave = SherpaOnnxReadWave(wav_filename);
  // 检查是否成功读取音频文件
  if (wave == NULL) {
    // 如果读取失败，打印错误信息并返回-1
    fprintf(stderr, "Failed to read %s\n", wav_filename);
    return -1;
  }

// 检查音频采样率是否符合要求的16000Hz
  if (wave->sample_rate != 16000) {
    // 如果采样率不匹配，打印错误信息并释放波形数据内存
    fprintf(stderr, "Expect the sample rate to be 16000. Given: %d\n",
            wave->sample_rate);
    SherpaOnnxFreeWave(wave);// 释放波形数据内存
    return -1;
  }

  // 初始化语音活动检测(VAD)模型配置结构体
SherpaOnnxOfflineSenseVoiceModelConfig sense_voice_config;
// 将结构体内存清零，避免未初始化字段的随机值
memset(&sense_voice_config, 0, sizeof(sense_voice_config));
// 设置模型文件路径
sense_voice_config.model = model_filename;
// 设置识别语言
sense_voice_config.language = language;
// 设置是否使用反标准化（将规范化文本还原为口语形式）
sense_voice_config.use_itn = use_inverse_text_normalization;

// 初始化离线模型配置结构体
SherpaOnnxOfflineModelConfig offline_model_config;
// 将结构体内存清零
memset(&offline_model_config, 0, sizeof(offline_model_config));
// 设置调试模式（0表示关闭）
offline_model_config.debug = 0;
// 设置推理线程数
offline_model_config.num_threads = 1;
// 设置ONNX运行时提供程序（如"cpu"、"cuda"等）
offline_model_config.provider = provider;
// 设置token文件路径（词汇表）
offline_model_config.tokens = tokens_filename;
// 关联语音活动检测配置
offline_model_config.sense_voice = sense_voice_config;

// 初始化识别器配置结构体
SherpaOnnxOfflineRecognizerConfig recognizer_config;
// 将结构体内存清零
memset(&recognizer_config, 0, sizeof(recognizer_config));
// 设置解码方法为贪心搜索（最快但可能不是最优）
recognizer_config.decoding_method = "greedy_search";
// 关联离线模型配置
recognizer_config.model_config = offline_model_config;

// 创建离线识别器实例
const SherpaOnnxOfflineRecognizer *recognizer =
    SherpaOnnxCreateOfflineRecognizer(&recognizer_config);

  // 检查识别器是否创建成功
if (recognizer == NULL) {
    // 如果失败，打印错误提示并释放之前分配的波形数据内存
    fprintf(stderr, "识别器创建失败，请检查配置参数！\n");
    SherpaOnnxFreeWave(wave);  // 释放音频波形数据
    return -1;  // 返回错误码
}
 
// 初始化语音活动检测(VAD)模型配置
SherpaOnnxVadModelConfig vadConfig;
// 将配置结构体内存清零，避免未初始化字段的随机值
memset(&vadConfig, 0, sizeof(vadConfig));
 
// Silero VAD模型专用配置
vadConfig.silero_vad.model = vad_filename;  // 设置VAD模型文件路径
vadConfig.silero_vad.threshold = 0.5;       // 设置语音检测的阈值（0-1）
vadConfig.silero_vad.min_silence_duration = 0.5;  // 最小静音持续时间（秒）
vadConfig.silero_vad.min_speech_duration = 0.5;   // 最小语音持续时间（秒）
vadConfig.silero_vad.max_speech_duration = 5;     // 最大语音持续时间（秒）
vadConfig.silero_vad.window_size = 512;           // 推理窗口大小（采样点数）
 
// 通用VAD配置
vadConfig.sample_rate = 16000;  // 设置音频采样率（需与输入音频一致）
vadConfig.num_threads = 1;      // 设置VAD处理的线程数
vadConfig.debug = 1;            // 开启调试模式（1=开启，0=关闭）



  // 创建语音活动检测器(VAD)实例
// 参数说明：
//   &vadConfig - VAD配置结构体指针
//   30 - 缓存队列大小（帧数）
const SherpaOnnxVoiceActivityDetector *vad =
      SherpaOnnxCreateVoiceActivityDetector(&vadConfig, 30);
 
// 检查VAD是否创建成功
if (vad == NULL) {
    // 如果失败，打印错误提示并释放已分配的资源
    fprintf(stderr, "VAD创建失败,请检查配置参数！\n");
    SherpaOnnxFreeWave(wave);          // 释放音频波形数据
    SherpaOnnxDestroyOfflineRecognizer(recognizer);  // 释放识别器
    return -1;  // 返回错误码
}
 
// 获取VAD处理的窗口大小（采样点数）
int32_t window_size = vadConfig.silero_vad.window_size;
int32_t i = 0;        // 当前处理位置索引
int is_eof = 0;       // 文件结束标志
 
// 主处理循环：逐窗口处理音频数据
while (!is_eof) {
    // 检查是否还有完整窗口的数据可处理
    if (i + window_size < wave->num_samples) {
        // 处理当前窗口的音频数据
        // 参数说明：
        //   vad - VAD实例指针
        //   wave->samples + i - 当前窗口的音频数据指针
        //   window_size - 当前窗口的采样点数
        SherpaOnnxVoiceActivityDetectorAcceptWaveform(vad, wave->samples + i,
                                                    window_size);
        i += window_size;  // 移动处理索引到下一窗口
    } else {
        // 处理剩余不足一个窗口的音频数据（文件末尾）
        SherpaOnnxVoiceActivityDetectorFlush(vad);
        is_eof = 1;  // 设置文件结束标志
    }


    // 处理VAD检测到的所有语音段
while (!SherpaOnnxVoiceActivityDetectorEmpty(vad)) {
    // 获取队列中的下一个语音段（但不移除）
    const SherpaOnnxSpeechSegment *segment =
        SherpaOnnxVoiceActivityDetectorFront(vad);
 
    // 创建离线识别流（每个语音段对应一个独立流）
    const SherpaOnnxOfflineStream *stream =
        SherpaOnnxCreateOfflineStream(recognizer);
 
    // 向识别流中输入音频数据
    // 参数说明：
    //   stream - 识别流指针
    //   wave->sample_rate - 音频采样率(16kHz)
    //   segment->samples - 语音段音频数据指针
    //   segment->n - 语音段采样点数
    SherpaOnnxAcceptWaveformOffline(stream, wave->sample_rate,
                                   segment->samples, segment->n);
 
    // 执行语音识别解码
    SherpaOnnxDecodeOfflineStream(recognizer, stream);
 
    // 获取识别结果
    const SherpaOnnxOfflineRecognizerResult *result =
        SherpaOnnxGetOfflineStreamResult(stream);
 
    // 计算时间戳（转换为秒）
    float start = segment->start / 16000.0f;    // 起始时间(秒)
    float duration = segment->n / 16000.0f;     // 持续时间(秒)
    float stop = start + duration;              // 结束时间(秒)
 
    // 输出识别结果（带时间戳）
    fprintf(stderr, "%.3f -- %.3f: %s\n", start, stop, result->text);
 
    // 释放资源
    SherpaOnnxDestroyOfflineRecognizerResult(result);  // 释放识别结果
    SherpaOnnxDestroyOfflineStream(stream);            // 释放识别流
    SherpaOnnxDestroySpeechSegment(segment);           // 释放语音段
    SherpaOnnxVoiceActivityDetectorPop(vad);           // 从队列中移除该语音段
}
 
// 移动到下一个处理窗口（流式处理）
i += window_size;
}  // 结束主处理循环
 
// 释放所有资源
SherpaOnnxDestroyOfflineRecognizer(recognizer);  // 释放识别器
SherpaOnnxDestroyVoiceActivityDetector(vad);     // 释放VAD检测器
SherpaOnnxFreeWave(wave);                       // 释放音频数据
 
return 0; 
}
